package genesisblock

import (
	"fmt"
	"io/ioutil"

	"github.com/golang/protobuf/proto"
	"github.com/spf13/cobra"
	"gopkg.in/yaml.v2"
	"hundsun.com/hsl/hschain/common/util/pb"
	"hundsun.com/hsl/hschain/contract/native/token/types"
	"hundsun.com/hsl/hschain/protos"
	"hundsun.com/hsl/hschain/protos/config"
	"hundsun.com/hsl/hschain/protos/consensus/raft"
	"hundsun.com/hsl/hschain/protos/ledger"
	"hundsun.com/hsl/hschain/protos/parachain"
)

//Cmd cmd
func Cmd() *cobra.Command {
	cmd := &cobra.Command{
		Use:   "genesis",
		Short: "Genesis block operation",
		Long:  "Genesis block operation",
	}

	cmd.AddCommand(
		generateCmd(),
		decodeCmd(),
	)
	return cmd
}

func generateCmd() *cobra.Command {
	cmd := &cobra.Command{
		Use:   "generate",
		Short: "Generate genesis block",
		Long:  "Generate genesis block",
		Run:   generate,
	}

	cmd.Flags().StringP("input", "i", "", "genesis block yaml config")
	cmd.Flags().StringP("output", "o", "", "genesis block output file")
	cmd.MarkFlagRequired("input")
	cmd.MarkFlagRequired("output")
	return cmd
}

func generate(cmd *cobra.Command, args []string) {
	defer func() {
		err := recover()
		if err != nil {
			fmt.Println(err)
		}
	}()
	input, _ := cmd.Flags().GetString("input")
	output, _ := cmd.Flags().GetString("output")

	fmt.Println("generating block from yaml: ", input)
	ymlConfBytes, err := ioutil.ReadFile(input)
	if err != nil {
		fmt.Println(err)
		return
	}
	var commYamlConfig CommonGenesisBlockYamlConfig
	err = yaml.Unmarshal(ymlConfBytes, &commYamlConfig)
	if err != nil {
		fmt.Println(err)
		return
	}
	err = commYamlConfig.ConvertToProtoMessage()
	if err != nil {
		fmt.Println(err)
		return
	}
	var pbGenesisTxPayload *config.GenesisTransactionPayload
	switch commYamlConfig.Consensus.Type {
	case config.DposConsensusType:
		var dposYamlConfig DPOSGenesisBlockYamlConfig
		err = yaml.Unmarshal(ymlConfBytes, &dposYamlConfig)
		if err != nil {
			fmt.Println(err)
			return
		}
		err = dposYamlConfig.ConvertToProtoMessage()
		if err != nil {
			fmt.Println(err)
			return
		}
		pbGenesisTxPayload = dposYamlConfig.genesisTxPayload
	case config.SoloConsensusType:
		var soloYamlConfig SoloGenesisBlockYamlConfig
		err = yaml.Unmarshal(ymlConfBytes, &soloYamlConfig)
		if err != nil {
			fmt.Println(err)
			return
		}
		err = soloYamlConfig.ConvertToProtoMessage()
		if err != nil {
			fmt.Println(err)
			return
		}
		pbGenesisTxPayload = soloYamlConfig.genesisTxPayload
	case raft.ConsensusType:
		var raftYamlConfig RAFTGenesisBlockYamlConfig
		err = yaml.Unmarshal(ymlConfBytes, &raftYamlConfig)
		if err != nil {
			fmt.Println(err)
			return
		}
		err = raftYamlConfig.ConvertToProtoMessage()
		if err != nil {
			fmt.Println(err)
			return
		}
		pbGenesisTxPayload = raftYamlConfig.genesisTxPayload
	case parachain.ConsensusType:
		var validatorYamConfig ValidatorGenesisBlockYamlConfig
		err = yaml.Unmarshal(ymlConfBytes, &validatorYamConfig)
		if err != nil {
			fmt.Println(err)
			return
		}
		err = validatorYamConfig.ConvertToProtoMessage()
		if err != nil {
			fmt.Println(err)
			return
		}
		pbGenesisTxPayload = validatorYamConfig.genesisTxPayload
	default:
		fmt.Printf("unsupport consensus type '%s'", commYamlConfig.Consensus.Type)
		return
	}

	txPayload, err := proto.Marshal(pbGenesisTxPayload)
	if err != nil {
		fmt.Println(err)
		return
	}
	pbTx := &ledger.Transaction{
		Ty:         types.NativeToken,
		From:       string(protos.GenesisCoinbase),
		To:         "",
		ContractId: nil,
		Value:      0,
		Fee:        0,
		Payload:    txPayload,
		Nonce:      0,
		CreateTime: commYamlConfig.GetTimestamp(),
		TTL:        0,
		Signature:  nil,
	}

	pbBlockHeader := &ledger.BlockHeader{
		ParentHash:             commYamlConfig.GetParentHash(),
		Number:                 0,
		Difficulty:             pbGenesisTxPayload.Difficulty,
		BlockTime:              commYamlConfig.GetTimestamp(),
		CheckBlock:             0,
		StateHashOfCheckBlock:  nil,
		Signature:              nil,
		BlockSequence:          0,
		BlockSequenceSignature: nil,
		Consensus:              nil,
	}

	block := &ledger.Block{
		Header:       pbBlockHeader,
		Transactions: []*ledger.Transaction{pbTx},
	}
	blockBytes, err := proto.Marshal(block)
	if err != nil {
		fmt.Println(err)
		return
	}
	err = ioutil.WriteFile(output, blockBytes, 0755)
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Println("block generated: ", output)
}

func decodeCmd() *cobra.Command {
	cmd := &cobra.Command{
		Use:   "decode",
		Short: "Decode genesis block",
		Long:  "Decode genesis block",
		Run:   decode,
	}

	cmd.Flags().StringP("input", "i", "", "genesis block file")
	cmd.Flags().StringP("output", "o", "", "genesis block yaml config")
	cmd.MarkFlagRequired("input")
	cmd.MarkFlagRequired("output")
	return cmd
}

func decode(cmd *cobra.Command, args []string) {
	input, _ := cmd.Flags().GetString("input")
	output, _ := cmd.Flags().GetString("output")

	fmt.Println("decoding genesis block: ", input)
	blockBytes, err := ioutil.ReadFile(input)
	if err != nil {
		fmt.Println(err)
		return
	}
	var block ledger.Block
	err = proto.Unmarshal(blockBytes, &block)
	if err != nil {
		fmt.Println(err)
		return
	}
	err = pb.CheckGenesisBlock(&block)
	if err != nil {
		fmt.Println(err)
		return
	}

	genesisTxPayload, err := pb.ParseGenesisTransactionPayload(block.Transactions[0].Payload)
	if err != nil {
		fmt.Println(err)
		return
	}

	var yamlConfig interface{}

	switch genesisTxPayload.Consensus.Type {
	case config.DposConsensusType:
		dposYamlConfig := &DPOSGenesisBlockYamlConfig{}
		err = dposYamlConfig.InitFromProtoMessage(&block)
		yamlConfig = dposYamlConfig
	case config.SoloConsensusType:
		soloYamlConfig := &SoloGenesisBlockYamlConfig{}
		err = soloYamlConfig.InitFromProtoMessage(&block)
		yamlConfig = soloYamlConfig
	case raft.ConsensusType:
		raftYamlConfig := &RAFTGenesisBlockYamlConfig{}
		err = raftYamlConfig.InitFromProtoMessage(&block)
		yamlConfig = raftYamlConfig
	case parachain.ConsensusType:
		validatorYamlConfig := &ValidatorGenesisBlockYamlConfig{}
		err = validatorYamlConfig.InitFromProtoMessage(&block)
		yamlConfig = validatorYamlConfig
	}

	if err != nil {
		fmt.Println(err)
		return
	}

	yamlConfigBytes, err := yaml.Marshal(yamlConfig)
	if err != nil {
		fmt.Println(err)
		return
	}
	err = ioutil.WriteFile(output, yamlConfigBytes, 0755)
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Println("yaml generated: ", output)
}
